/*
* Copyright Beijing 58 Information Technology Co.,Ltd.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.bj58.spat.gaea.client.communication.socket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ConcurrentHashMap;
import java.nio.channels.NotYetConnectedException;
import com.bj58.spat.gaea.client.configuration.commmunication.SocketPoolProfile;
import com.bj58.spat.gaea.client.utility.AutoResetEvent;
import com.bj58.spat.gaea.client.utility.logger.ILog;
import com.bj58.spat.gaea.client.utility.logger.LogFactory;
import com.bj58.spat.gaea.protocol.exception.DataOverFlowException;
import com.bj58.spat.gaea.protocol.exception.ProtocolException;
import com.bj58.spat.gaea.protocol.exception.TimeoutException;
import com.bj58.spat.gaea.protocol.sfp.v1.SFPStruct;
import com.bj58.spat.gaea.protocol.utility.ByteConverter;
import com.bj58.spat.gaea.protocol.utility.ProtocolConst;
/**
* CSocket
*
* @author Service Platform Architecture Team (spat@58.com)
*/
public class CSocket {
private static final ILog logger = LogFactory.getLogger(CSocket.class);
private byte[] DESKey;//DES密钥
private boolean rights;//是否启用认证
private Socket socket;
private ScoketPool pool;
private SocketChannel channel;
private ByteBuffer receiveBuffer, sendBuffer;
private SocketPoolProfile socketConfig;
private boolean _inPool = false;
private boolean _connecting = false;
private DataReceiver dataReceiver = null;
private boolean waitDestroy = false;
private final Object sendLockHelper = new Object();
private final Object receiveLockHelper = new Object();
private CByteArrayOutputStream receiveData = new CByteArrayOutputStream();
private ConcurrentHashMap<Integer, WindowData> WaitWindows = new ConcurrentHashMap<Integer, WindowData>();
protected CSocket(InetSocketAddress endpoint, ScoketPool _pool, SocketPoolProfile config) throws IOException {
this.socketConfig = config;
this.pool = _pool;
channel = SocketChannel.open(endpoint);
channel.configureBlocking(false);
socket = channel.socket();
channel.socket().setSendBufferSize(config.getSendBufferSize());
channel.socket().setReceiveBufferSize(config.getRecvBufferSize());
receiveBuffer = ByteBuffer.allocate(config.getBufferSize());
sendBuffer = ByteBuffer.allocate(config.getMaxPakageSize());
_connecting = true;
dataReceiver = DataReceiver.instance();
dataReceiver.RegSocketChannel(this);
logger.info("MaxPakageSize:" + config.getMaxPakageSize());
logger.info("SendBufferSize:" + config.getSendBufferSize());
logger.info("RecvBufferSize:" + config.getRecvBufferSize());
logger.info("create a new connection :" + this.toString());
}
public int send(byte[] data) throws IOException, Throwable {
try {
synchronized (sendLockHelper) {
int pakageSize = data.length + ProtocolConst.P_END_TAG.length;
if (sendBuffer.capacity() < pakageSize) {
throw new DataOverFlowException("数据包(size:" + pakageSize + ")超过最大限制,请修改或增加配置文件中的<SocketPool maxPakageSize=\"" + socketConfig.getMaxPakageSize() + "\"/>节点属性!");
}
int count = 0;
sendBuffer.clear();
sendBuffer.put(data);
sendBuffer.put(ProtocolConst.P_END_TAG);
sendBuffer.flip();
int retryCount = 0;
while(sendBuffer.hasRemaining()) {
count += channel.write(sendBuffer);
if(retryCount++ > 10) {
throw new Exception("retry write count(" + retryCount + ") above 10");
}
}
return count;
}
} catch (IOException ex) {
_connecting = false;
throw ex;
} catch (NotYetConnectedException ex) {
_connecting = false;
throw ex;
}
}
public byte[] receive(int sessionId, int queueLen) throws IOException, TimeoutException, Exception {
WindowData wd = WaitWindows.get(sessionId);
if (wd == null) {
throw new Exception("Need invoke 'registerRec' method before invoke 'receive' method!");
}
AutoResetEvent event = wd.getEvent();
int timeout = getReadTimeout(socketConfig.getReceiveTimeout(), queueLen);
if (!event.waitOne(timeout)) {
throw new TimeoutException("Receive data timeout or error!timeout:" + timeout + "ms,queue length:" + queueLen);
}
byte[] data = wd.getData();
int offset = SFPStruct.Version;
int len = ByteConverter.bytesToIntLittleEndian(data, offset);
if (len != data.length) {
throw new ProtocolException("The data length inconsistent!datalen:" + data.length + ",check len:" + len);
}
return data;
}
public void registerRec(int sessionId) {
AutoResetEvent event = new AutoResetEvent();
WindowData wd = new WindowData(event);
WaitWindows.put(sessionId, wd);
}
public void unregisterRec(int sessionId) {
WaitWindows.remove(sessionId);
}
public void close() {
pool.release(this);
}
public void dispose() throws Exception {
dispose(false);
}
public void dispose(boolean flag) {
if (flag) {
logger.warning("destory a connection");
try {
dataReceiver.UnRegSocketChannel(this);
} finally {
pool.destroy(this);
}
} else {
close();
}
}
protected void disconnect() throws IOException {
if (channel != null) {
channel.close();
}
_connecting = false;
}
private int getReadTimeout(int baseReadTimeout, int queueLen) {
if (!socketConfig.isProtected()) {
return baseReadTimeout;
}
if (queueLen <= 0) {
queueLen = 1;
}
int result = baseReadTimeout;
int flag = (queueLen - 100) / 10;
if (flag >= 0) {
if (flag == 0) {
flag = 1;
}
result = baseReadTimeout / (2 * flag);
} else if (flag < -7) {
result = baseReadTimeout - ((flag) * (baseReadTimeout / 10));
}
if (result > 2 * baseReadTimeout) {
result = baseReadTimeout;
} else if (result < 5) {
result = 5; //min timeout is 5ms
}
if (queueLen > 50) {
logger.warn("-----IsProtected:true,queueLen:" + queueLen + ",timeout:" + result + ",baseReadTimeout:" + baseReadTimeout);
}
return result;
}
private int index = 0;
private boolean handling = false;
protected void frameHandle() throws Exception {
if (handling) {
return;
}
synchronized (receiveLockHelper) {
handling = true;
try {
if (waitDestroy && isIdle()) {
logger.info("Shrinking the connection:" + this.toString());
dispose(true);
return;
}
receiveBuffer.clear();
try {
int re = channel.read(receiveBuffer);
if (re < 0) {
this.closeAndDisponse();
logger.error("server is close.this socket will close.");
return;
}
} catch (IOException ex) {
_connecting = false;
throw ex;
} catch (NotYetConnectedException e) {
_connecting = false;
throw e;
}
receiveBuffer.flip();
if (receiveBuffer.remaining() == 0) {
return;
}
while (receiveBuffer.remaining() > 0) {
byte b = receiveBuffer.get();
receiveData.write(b);
if (b == ProtocolConst.P_END_TAG[index]) {
index++;
if (index == ProtocolConst.P_END_TAG.length) {
byte[] pak = receiveData.toByteArray(0, receiveData.size() - ProtocolConst.P_END_TAG.length);
int pSessionId = ByteConverter.bytesToIntLittleEndian(pak, SFPStruct.Version + SFPStruct.TotalLen);
WindowData wd = WaitWindows.get(pSessionId);
if (wd != null) {
wd.setData(pak);
wd.getEvent().set();
}
index = 0;
receiveData.reset();
continue;
}
} else if (index != 0) {
if(b == ProtocolConst.P_END_TAG[0]) {
index = 1;
} else {
index = 0;
}
}
}
} catch(Exception ex){
index = 0;
throw ex;
}finally {
handling = false;
}
}
}
@Override
protected void finalize() throws Throwable {
try {
if (_connecting || (channel != null && channel.isOpen())) {
dispose(true);
}
} catch (Throwable t) {
logger.error("Pool Release Error!:", t);
} finally {
super.finalize();
}
}
public void closeAndDisponse(){
this.close();
dispose(true);
}
public boolean connecting() {
return _connecting;
}
protected boolean inPool() {
return _inPool;
}
protected void setInPool(boolean inPool) {
_inPool = inPool;
}
protected SocketChannel getChannle() {
return channel;
}
/*
* 该链接是否是空闲状态
*/
protected boolean isIdle() {
return !(WaitWindows.size() > 0);
}
protected void waitDestroy() {
this.waitDestroy = true;
}
public boolean isRights() {
return rights;
}
public void setRights(boolean rights) {
this.rights = rights;
}
public byte[] getDESKey() {
return DESKey;
}
public void setDESKey(byte[] dESKey) {
DESKey = dESKey;
}
@Override
public String toString() {
try {
return (socket == null) ? "" : socket.toString();
} catch (Throwable ex) {
return "Socket[error:" + ex.getMessage() + "]";
}
}
}